#ifndef _CONNECTIONPOINT_HELPER_H_
#define _CONNECTIONPOINT_HELPER_H_

#include <dlcom/conntopoint/connectionpoint.h>
#include <vector>

namespace by {
//////////////////////////////////////////////////////////////////////////
struct ST_ConnectionPoint
{
	IID	iid;
	com_ptr<IConnectionPoint> p;
};
typedef std::vector<ST_ConnectionPoint> CONNECTIONPOINTS, *LPCONNECTIONPOINTS;

class CConnectionPointHelper;
class CEnumConnectionPoints
	: public IEnumConnectionPoints
	, public CUnknownImp
{
public:
	CEnumConnectionPoints(CONNECTIONPOINTS& ConnectionPoints)
		: m_ulEnumConnectionPointsPos(0)
		, m_ConnectionPoints(ConnectionPoints)
	{}

	UNKNOWN_IMP1(IEnumConnectionPoints);

	STDMETHOD(Next)( ULONG cConnections, LPCONNECTIONPOINT *ppCP, ULONG *pcFetched)
	{
		if(pcFetched)
			*pcFetched = 0;
		RASSERT(cConnections, S_OK); 
		RASSERT(ppCP, S_FALSE);

		size_t sSize = m_ConnectionPoints.size();
		if(0 == sSize)
		{
			return S_FALSE;
		}

		ULONG i = 0;
		while((i < cConnections) && (m_ulEnumConnectionPointsPos < sSize))
		{
			if(m_ConnectionPoints[m_ulEnumConnectionPointsPos].p)
				m_ConnectionPoints[m_ulEnumConnectionPointsPos].p->QueryInterface(_uuidof(IRsConnectionPoint), (void**)&ppCP[i]);

			++m_ulEnumConnectionPointsPos;
			++i;
		}

		if(i < cConnections)
		{
			if(pcFetched)
				*pcFetched = 0;
			return S_FALSE;
		}

		return S_OK;
	}

	STDMETHOD(Skip)( ULONG cConnections )
	{
		m_ulEnumConnectionPointsPos += cConnections;
		return S_OK;
	}

	STDMETHOD(Reset)( void )
	{
		m_ulEnumConnectionPointsPos = 0;
		return S_OK;
	}

	STDMETHOD(Clone)(IEnumConnectionPoints **ppEnum)
	{
		RASSERT(ppEnum, E_POINTER);

		CEnumConnectionPoints* pNew = new CEnumConnectionPoints(m_ConnectionPoints);
		HRESULT hr = pNew->QueryInterface(_uuidof(IEnumConnectionPoints), (void**)ppEnum);
		if(NULL == *ppEnum)
		{
			delete pNew;
			pNew = NULL;
		}

		return hr;
	}

private:
	ULONG m_ulEnumConnectionPointsPos;
	CONNECTIONPOINTS& m_ConnectionPoints;
};

#define CallConnectPointFunc(obj, iid, func)	{	\
	com_ptr<IEnumConnections> pEnum;	\
	if(SUCCEEDED(obj.EnumConnections((IEnumConnections**)&pEnum)) && pEnum){	\
	HRESULT hNext = S_OK;	\
	while(S_OK == hNext){	\
	CONNECTDATA data[1];	\
	 \
	if(S_OK == (hNext=pEnum->Next(1, data, NULL))){	\
	com_ptr<iid> p = data[0].pUnk;	\
	if(p) p->func;	\
	}}}\
}

#define CallConnectPointFunc_Tc(obj, iid, func, rc, tc)	{	\
	com_ptr<IEnumConnections> pEnum;	\
	if(SUCCEEDED(obj.EnumConnections((IEnumConnections**)&pEnum)) && pEnum){	\
	HRESULT hNext = S_OK;	\
	while(S_OK == hNext){	\
	CONNECTDATA data[1];	\
	 \
	if(S_OK == (hNext=pEnum->Next(1, data, NULL))){	\
	com_ptr<iid> p = data[0].pUnk;	\
	if(p){ rc=p->func;	if(tc(rc)) break;}\
	}}}\
}


template<class T>
class CConnectionPointContainerHelper
	: public IConnectionPointContainer
{
protected:
	CONNECTIONPOINTS m_ConnectionPoints;	

public:
	STDMETHOD(EnumConnectionPoints)(IEnumConnectionPoints **ppEnum)
	{
		RASSERT(ppEnum, E_POINTER);

		CEnumConnectionPoints* pNew = new CEnumConnectionPoints(m_ConnectionPoints);
		HRESULT hr = pNew->QueryInterface(_uuidof(IEnumConnectionPoints), (void**)ppEnum);
		if(NULL == *ppEnum) 
		{
			delete pNew;
			pNew = NULL;
		}

		return hr;
	}

	STDMETHOD(FindConnectionPoint)(REFIID riid, IConnectionPoint **ppCP)
	{
		RASSERT(ppCP, E_POINTER);
		RASSERT( m_ConnectionPoints.size() > 0, E_NOINTERFACE ); 

		CONNECTIONPOINTS::const_iterator it = m_ConnectionPoints.begin();
		for(; it != m_ConnectionPoints.end(); it++)
		{
			if(it->iid == riid) 
			{
				RASSERT(it->p, E_POINTER);
				RFAILED(it->p->QueryInterface(_uuidof(IConnectionPoint), (void**)ppCP));
				RASSERT(*ppCP, E_POINTER);
				return S_OK;
			}
		}

		return E_NOINTERFACE; 
	}

	virtual void EmptyConnectionPointContainer()
	{
		m_ConnectionPoints.clear();
	}

	virtual BOOL AddConnectionPoint(IID iid, IConnectionPoint* p)
	{
		RASSERT(p, FALSE);

		ST_ConnectionPoint point;
		point.iid = iid;
		point.p = p;
		m_ConnectionPoints.push_back(point);
		return TRUE;
	}

	virtual BOOL AddConnectionPoint(IID iid, CConnectionPointHelper &connectPointHelper, BOOL bAddRef = TRUE)
	{
		T* pThis = static_cast<T*>(this);
		com_ptr<IConnectionPointContainer> p = pThis;
		RASSERT(p, FALSE);
		connectPointHelper.SetInfo(iid, p);

		if(bAddRef)
			connectPointHelper.AddRef(); 

		com_ptr<IConnectionPoint> pPoint(&connectPointHelper);
		return AddConnectionPoint(iid, pPoint);
	}

	virtual BOOL DelConnectionPoint(REFIID riid)
	{
		CONNECTIONPOINTS::iterator it = m_ConnectionPoints.begin();
		for(; it != m_ConnectionPoints.end(); it++)
		{
			if(it->iid == riid) 
			{
				m_ConnectionPoints.erase(it);
				return TRUE;
			}
		}
		return FALSE;
	}

	virtual BOOL DelConnectionPoint(REFIID riid, CConnectionPointHelper &connectPointHelper)
	{
		connectPointHelper.EmptyConnection();
		return DelConnectionPoint(riid);
	}
};


struct ST_CONNECTDATA
{
	DWORD dwCookie;
	com_ptr<IBase> p;
};
typedef std::vector<ST_CONNECTDATA> CONNECTIONS, *LPCONNECTIONS;

class CConnectionPointHelper
	: public IConnectionPoint
	, public CUnknownImp
{
public:
	CConnectionPointHelper(IID iid = GUID_NULL, IConnectionPointContainer* pContainer = NULL)
		: m_iidConnectionPoint(iid)
		, m_pContainer(pContainer)
		, m_dwCookieNum(0)
	{}

	UNKNOWN_IMP1(IConnectionPoint);

	void SetInfo(IID iid, IConnectionPointContainer* pContainer = NULL)
	{
		m_iidConnectionPoint = iid;
		m_pContainer = pContainer;
	}

	STDMETHOD(GetConnectionInterface)(IID *pIID)
	{
		RASSERT(pIID, E_POINTER);

		*pIID = m_iidConnectionPoint;
		return S_OK;
	}
	STDMETHOD(GetConnectionPointContainer)(IConnectionPointContainer **ppCPC)
	{
		RASSERT(ppCPC, E_POINTER);

		*ppCPC = m_pContainer;
		RASSERT(*ppCPC, E_POINTER);

		return S_OK;
	}
	STDMETHOD(Advise)(IBase *pUnkSink, DWORD *pdwCookie)
	{
		RASSERT(pUnkSink, E_POINTER);
		RASSERT(pdwCookie, E_POINTER);

		*pdwCookie = ++m_dwCookieNum;

		ST_CONNECTDATA data;
		data.dwCookie = *pdwCookie;
		data.p = pUnkSink;
		m_Connections.push_back(data);

		return S_OK;	
	}
	STDMETHOD(Unadvise)(DWORD dwCookie)
	{
		CONNECTIONS::iterator it = m_Connections.begin();
		for(; it != m_Connections.end(); it++)
		{
			if(it->dwCookie == dwCookie) 
			{
				m_Connections.erase(it);
				return S_OK;
			}
		}

		return E_NOINTERFACE; 
	}
	STDMETHOD(EnumConnections)(IEnumConnections **ppEnum)
	{
		RASSERT(ppEnum, E_POINTER);

		CEnumConnections* pNew = new CEnumConnections(m_Connections);
		HRESULT hr = pNew->QueryInterface(_uuidof(IEnumConnections), (void**)ppEnum);
		if(NULL == *ppEnum) 
		{
			delete pNew;
			pNew = NULL;
		}

		return hr;
	}

	virtual void EmptyConnection()
	{
		CONNECTIONS holdRef = m_Connections;
		m_Connections.clear();
		m_pContainer = INULL;
	}

	virtual HRESULT FindConnection(DWORD dwCookie, IID iid, void** p)
	{
		RASSERT(m_Connections.size() > 0, E_NOINTERFACE);

		for(size_t i = 0; i < m_Connections.size(); i++)
		{
			ST_CONNECTDATA &data = m_Connections[i];
			if(data.dwCookie == dwCookie)
			{
				RASSERT(data.p, E_NOINTERFACE);
				return data.p->QueryInterface(iid, p);
			}
		}

		return E_NOINTERFACE;
	}
	//////////////////////////////////////////////////////////////////////////
	class CEnumConnections
		: public IEnumConnections
		, public CUnknownImp
	{
	public:
		CEnumConnections(CONNECTIONS& Connections)
		: m_ulEnumConnectionPos(0)
		, m_Connections(Connections)
		{}
		UNKNOWN_IMP1(IEnumConnections);

		STDMETHOD(Next)( ULONG cConnections, LPCONNECTDATA rgcd, ULONG *pcFetched )
		{
			if(pcFetched)
				*pcFetched = 0;

			RASSERT(cConnections, S_OK); 
			RASSERT(rgcd, S_FALSE);

			size_t sSize = m_Connections.size();
			if(0 == sSize)
			{
				return S_FALSE;
			}

			ULONG i = 0;
			while((i < cConnections) && (m_ulEnumConnectionPos < sSize))
			{
				rgcd[i].dwCookie = m_Connections[m_ulEnumConnectionPos].dwCookie;
				if(m_Connections[m_ulEnumConnectionPos].p)
					m_Connections[m_ulEnumConnectionPos].p->QueryInterface(_uuidof(IBase), (void**)&rgcd[i].pUnk);

				++m_ulEnumConnectionPos;
				++i;
			}

			if(i < cConnections)
			{
				if(pcFetched)
					*pcFetched = 0;

				return S_FALSE;
			}

			return S_OK;
		}

		STDMETHOD(Skip)( ULONG cConnections )
		{
			m_ulEnumConnectionPos += cConnections;
			return S_OK;
		}

		STDMETHOD(Reset)( void )
		{
			m_ulEnumConnectionPos = 0;
			return S_OK;
		}

		STDMETHOD(Clone)(IEnumConnections **ppEnum)
		{
			RASSERT(ppEnum, E_POINTER);

			CEnumConnections* pNew = new CEnumConnections(m_Connections);
			HRESULT hr = pNew->QueryInterface(_uuidof(IEnumConnections), (void**)ppEnum);
			if(NULL == *ppEnum) 
			{
				delete pNew;
				pNew = NULL;
			}

			return hr;
		}
	private:
		ULONG	m_ulEnumConnectionPos;
		CONNECTIONS &m_Connections;
	};

protected:
	IID		m_iidConnectionPoint;
	com_ptr<IConnectionPointContainer>  m_pContainer;
	DWORD	m_dwCookieNum;
	CONNECTIONS m_Connections;	
};

} 

#endif 